[id].astro raw
1 ---
2 export const prerender = false;
3 import Base from '../../layouts/Base.astro';
4 import { getOrderForView, orderView } from '../../lib/orders';
5
6 const token = Astro.url.searchParams.get('t') || '';
7 const order = getOrderForView(Astro.params.id!, token);
8 if (!order) {
9 return new Response(null, { status: 404, statusText: 'Not Found' });
10 }
11 const view = orderView(order);
12 const statusLabels: Record<string, string> = {
13 pending: 'Изчаква плащане',
14 paid: 'Платена',
15 failed: 'Неуспешно плащане',
16 expired: 'Изтекла',
17 shipped: 'Изпратена',
18 delivered: 'Доставена',
19 cancelled: 'Отказана',
20 };
21 const methodLabels: Record<string, string> = {
22 stripe: 'Карта (Stripe)',
23 lightning: 'Lightning (Bitcoin)',
24 onchain: 'Bitcoin (onchain)',
25 bank: 'Банков превод',
26 cod: 'Наложен платеж',
27 };
28 ---
29 <Base title="Поръчка">
30 <section class="section">
31 <div class="container">
32 <h1>Поръчка <span class="order-id">{view.id.slice(0, 8)}</span></h1>
33 <p class="order-status" data-status={view.status}>
34 Статус: <strong>{statusLabels[view.status] || view.status}</strong>
35 </p>
36
37 <div class="order-items">
38 {view.items.map((item: any) => (
39 <div class="order-item">
40 <span>{item.name} × {item.qty} бр.</span>
41 <span>{((item.price_cents * item.qty) / 100).toFixed(2)} EUR</span>
42 </div>
43 ))}
44 <div class="order-item order-sub">
45 <span>Междинна сума</span>
46 <span>{(view.subtotal_cents / 100).toFixed(2)} EUR</span>
47 </div>
48 <div class="order-item order-sub">
49 <span>Доставка</span>
50 <span>{view.shipping_cents === 0 ? 'Безплатна' : (view.shipping_cents / 100).toFixed(2) + ' EUR'}</span>
51 </div>
52 <div class="order-item order-total">
53 <strong>Общо</strong>
54 <strong>{(view.total_cents / 100).toFixed(2)} EUR</strong>
55 </div>
56 </div>
57
58 <h2>Плащане: {methodLabels[view.payment_method] || view.payment_method}</h2>
59 <div id="payment-area" data-order={view.id} data-token={token} data-method={view.payment_method} data-status={view.payment_status}>
60 {view.payment_method === 'bank' && view.status === 'pending' && (
61 <p>Ще получите банковите данни за превод на посочения имейл. Поръчката се обработва след постъпване на сумата.</p>
62 )}
63 {view.payment_method === 'cod' && (
64 <p>Плащате в брой при получаване на пратката.</p>
65 )}
66 {view.payment_method === 'lightning' && view.status === 'pending' && view.payment?.pr && (
67 <div class="ln-invoice">
68 <p>Платете Lightning фактурата с вашия портфейл:</p>
69 <code class="invoice-code">{view.payment.pr}</code>
70 <div style="margin-top:0.75rem">
71 <a class="btn btn-primary" href={`lightning:${view.payment.pr}`}>Отвори в портфейл</a>
72 <button class="btn btn-outline" id="copy-invoice" data-pr={view.payment.pr}>Копирай</button>
73 </div>
74 </div>
75 )}
76 {view.payment_method === 'onchain' && view.status === 'pending' && view.payment?.checkoutUrl && (
77 <p><a class="btn btn-primary" href={view.payment.checkoutUrl}>Плати с Bitcoin (BTCPay)</a></p>
78 )}
79 {view.payment_status === 'paid' && (
80 <p>✓ Плащането е получено.</p>
81 )}
82 </div>
83
84 <p>Ще получите потвърждение на имейл.</p>
85 <a href="/shop" class="btn btn-primary">Продължете да пазарувате</a>
86 </div>
87 </section>
88 </Base>
89
90 <script>
91 // copy invoice
92 document.getElementById('copy-invoice')?.addEventListener('click', (e) => {
93 const pr = (e.currentTarget as HTMLElement).dataset.pr!;
94 navigator.clipboard?.writeText(pr);
95 });
96
97 // poll payment status for methods that settle asynchronously
98 const area = document.getElementById('payment-area')!;
99 const method = area.dataset.method!;
100 const status = area.dataset.status!;
101 if (status === 'pending' && ['stripe', 'lightning', 'onchain'].includes(method)) {
102 const orderId = area.dataset.order!;
103 const token = area.dataset.token!;
104 const poll = async () => {
105 try {
106 const r = await fetch(`/api/orders/${orderId}?t=${token}`);
107 if (r.ok) {
108 const o = await r.json();
109 if (o.payment_status === 'paid') {
110 location.reload();
111 return;
112 }
113 }
114 } catch {}
115 setTimeout(poll, 5000);
116 };
117 setTimeout(poll, 5000);
118 }
119 </script>
120
121 <style>
122 .order-id { font-family: monospace; font-size: 0.8em; color: var(--text-light); }
123 .order-status { margin-bottom: 1.5rem; }
124 .order-items { max-width: 480px; margin-bottom: 2rem; }
125 .order-item { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid var(--border); }
126 .order-total { border-bottom: none; font-size: 1.1rem; }
127 #payment-area { margin-bottom: 2rem; }
128 .invoice-code { display: block; word-break: break-all; background: var(--bg, #f5f5f5); padding: 0.75rem; border-radius: 8px; font-size: 0.8rem; max-width: 640px; }
129 </style>
130